﻿using System;
using System.Linq;
using System.Text.RegularExpressions;
using NDBGen.Descriptors.Columns.Tokens;
using NDBGen.Descriptors.Tables.Tokens;
using NDBGen.Plugins.DataAccess;

namespace NDBGen.Plugins.Descriptors.Tables.Tokens
{
    [TableTokenCodeGenerator(typeof(EnumTokenDescriptor))]
    public sealed class DotNetEnumTokenCodeGenerator : DotNetTableTokenCodeGeneratorBase, IAuxilliaryCodeGenerator
    {
        public void WriteAuxilliaryCode(Table table, IIndentedTextWriter writer)
        {
            // if the table is just an ID and a name column, it can be replaced with the enum
            // otherwise we're only substituting the id property type
            var isReplacement = table.CanBeRepresentedByEnum;

            var identifierColumn = table.Columns.Single(c => c.Descriptors.Any(m => m.GetType() == typeof(EnumIdentifierTokenDescriptor)));
            var valueColumn = table.Columns.Single(c => c.Descriptors.Any(m => m.GetType() == typeof(EnumValueTokenDescriptor)));

            if (table.ScriptedData == null || !table.ScriptedData.Any()) throw new InvalidOperationException(String.Format("{0}.{1} is defined as an enum, but no lookup data has been defined for it.", table.Schema, table.Name));
            if (table.ScriptedData.Length > 1) throw new InvalidOperationException(String.Format("{0}.{1} is defined as an enum, but has data scripted in multiple files. Only one file should be used when scripting data for an enum.", table.Schema, table.Name));            
            var scriptedData = table.ScriptedData.Single();

            var identifierLookupColumn = scriptedData.Columns.SingleOrDefault(c => c.Name == identifierColumn.Name);
            if (identifierLookupColumn == null) throw new InvalidOperationException(String.Format("{0}.{1} is defined as an enum, but its lookup data doesn't specify identifiers", table.Schema, table.Name));

            var valueLookupColumn = scriptedData.Columns.SingleOrDefault(c => c.Name == valueColumn.Name);
            if (valueLookupColumn == null) throw new InvalidOperationException(String.Format("{0}.{1} is defined as an enum, but its lookup data doesn't specify values", table.Schema, table.Name));

            var identifierLookupColumnIndex = scriptedData.Columns.ToList().IndexOf(identifierLookupColumn);
            var valueLookupColumnIndex = scriptedData.Columns.ToList().IndexOf(valueLookupColumn);

            if (table.Descriptors.Any(m => m.GetType() == typeof(FlagsEnumTokenDescriptor)))
            {
                writer.WriteLine(TableMetadataHelper.CustomAttributes.Flags);
            }
            writer.WriteLine(String.Format(
                "public enum {0}{2} : {1}", this.Descriptor.TableName.ToPascalCase().Depluralize(), GetBaseType(valueColumn.DataType), isReplacement ? String.Empty : "ID"));
            writer.WriteLine(
                "{");

            using (writer.Indent())
            {
                foreach (var entry in scriptedData.Entries)
                {
                    var identifier = entry.Values[identifierLookupColumnIndex].ToPascalCase();
                    identifier = Regex.Replace(identifier, @"[\W\s]+", String.Empty);

                    writer.Write(String.Format(
                        "{0} = {1}", identifier, entry.Values[valueLookupColumnIndex]));

                    if (entry != scriptedData.Entries.Last())
                        writer.Write(',');

                    writer.WriteLine();
                }
            }
            writer.WriteLine(
                "}");
        }

        private String GetBaseType(DataType dataType)
        {
            switch (dataType)
            {
                case DataType.BigInt: return "long";
                case DataType.Int: return "int";
                case DataType.SmallInt: return "short";
                case DataType.TinyInt: return "byte";
                default: throw new NotSupportedException();
            }
        }
    }
}